﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExtremeLearningMachine;
using System.IO;
using Jama.Common;

namespace HarmonnySearch
{
    public class HS
    {
        public HS(int seed, int memorySize, int maxHiddenNodes, int maxIterations, DataProvider prov, EActivationFunctionType activationFunctionType, EEvaluationFunctionType evaluationFunctionType, EPerformanceInfo performanceInfo, bool doInputSearch)
        {
            _MaxIterations = maxIterations;
            _MemorySize = memorySize;
            _MaxHiddenNodes = maxHiddenNodes;
            _MaxInputNodes = prov.InputsN;
            _Prov = prov;
            _Seed = seed;
            _Rand = new Random(_Seed);
            _ActivationFunctionType = activationFunctionType;
            _EvaluationFunctionType = evaluationFunctionType;
            _PerformanceInfo = performanceInfo;
            _DoInputSearch = doInputSearch;

            _Initialize();
        }

        bool _DoInputSearch = false;
        List<HSParticle> _Memory = null;
        int _MaxIterations = -1;
        int _MaxHiddenNodes = -1;
        long _MaxInputNodes = -1;
        int _MemorySize = -1;
        int _Seed = -1;
        int SingularCount = 0;
        Random _Rand = null;
        DataProvider _Prov = null;
        EActivationFunctionType _ActivationFunctionType;
        EEvaluationFunctionType _EvaluationFunctionType;
        EPerformanceInfo _PerformanceInfo;
        double HMCR = 0.95;
       
        private void _Initialize()
        {
            _Memory = new List<HSParticle>(_MemorySize);
            for (int i = 0; i < _MemorySize; i++)
                _Memory.Add(new HSParticle());

            _Memory.ForEach(m => m.Values = new List<byte>((int)(_MaxInputNodes + _MaxHiddenNodes)));


            int increment = _MaxHiddenNodes / _MemorySize;

            int minvalue = 0;
            int maxValue = increment;

            foreach (HSParticle particle in _Memory)
            {
                while (particle.Values.Count(p => p.Equals(1)) <= (_MaxInputNodes * 2 / 3))
                {
                    particle.Values.Clear();
                    for (int i = 0; i < _MaxInputNodes; i++)
                    {
                        if (_DoInputSearch)
                            particle.Values.Add((byte)Math.Round(_Rand.NextDouble()));
                        else
                            particle.Values.Add(1);
                    }
                }

                particle.Values.AddRange(Extensions.CreateList(_MaxHiddenNodes, minvalue, maxValue));
                minvalue += increment;
                maxValue += increment;
            }
        }

        public object[] Run() 
        {
            List<HSParticle> badParticles = new List<HSParticle>();
            foreach (HSParticle particle in _Memory)
                try
                {
                    _Evaluate(particle);
                }
                catch (RuntimeException ex)
                {
                    badParticles.Add(particle);
                }
                catch (ArrayIndexOutOfBoundsException ex)
                {
                    badParticles.Add(particle);
                }
                catch (IllegalArgumentException ex)
                {
                    badParticles.Add(particle);
                }
                catch (IndexOutOfRangeException ex)
                {
                    badParticles.Add(particle);
                }

            if (badParticles.Count > 0)
                badParticles.ForEach(i => _Memory.Remove(i));

            _Memory.SortAscending();
            int count = 0;

            for (count = 0; (count < _MaxIterations) && _StoppingCriterion(count); )
            {
                bool ok = _GenerateNewHarmonny();
                if (ok)
                    count++;
            }
            _Memory.SortAscending();
            return new object[] {_Memory[0], count};
        }
       
        private bool _StoppingCriterion(int count)
        {
            double mean = 0;
            foreach (HSParticle p in _Memory)
                mean += p.Fitness;

            mean = mean / _MemorySize;
            if ( mean /_Memory[_Memory.Count-1].Fitness >= 0.9)
                return false;

            return true;
        }

        private int _GetMemoryIndexForNewHarmony()
        {
            int rand = -1;
            
            if(_Rand.NextDouble() < HMCR)
                rand = _Rand.Next(_Memory.Count);
            
            return rand;
        }

        private bool _GenerateNewHarmonny()
        {
            bool ok = true;
            HSParticle particle = new HSParticle();
            particle.Values = new List<byte>((int)(_MaxInputNodes + _MaxHiddenNodes));

            for (int i = 0; i < _MaxInputNodes + _MaxHiddenNodes; i++)
            {
                int index = _GetMemoryIndexForNewHarmony();

                if (i < _MaxInputNodes && !_DoInputSearch)
                {
                    particle.Values.Add(1);
                }
                else
                {
                    if (index == -1)
                        particle.Values.Add((byte)Math.Round(_Rand.NextDouble()));
                    else
                        particle.Values.Add(_Memory[index].Values[i]);
                }
            }
            try
            {
                _Evaluate(particle);
                _Memory.Add(particle);
                _Memory.SortAscending();
                if (_Memory.Count > _MemorySize)
                    _Memory.RemoveAt(_MemorySize);
            }
            catch (RuntimeException ex)
            {
                ok = false;
                SingularCount++;
            }
            catch (ArrayIndexOutOfBoundsException ex)
            {
                ok = false;
                SingularCount++;
            }
            catch (IllegalArgumentException ex)
            {
                ok = false;
                SingularCount++;
            }
            catch (IndexOutOfRangeException ex)
            {
                ok = false;
                SingularCount++;
            }

            return ok;
        }

        private void _Evaluate(HSParticle particle)
        {
            double EPMATrain = 0, EMQTrain = 0, DEVTrain = 0, SRTrain = 0, RMSETrain = 0, EPMAVal = 0, EMQVal = 0, DEVVal = 0, SRVal = 0, RMSEVal = 0; 

            ELMConfiguration config = _GetELMConfigurationFromHSParticle(particle);
            ELM elm = null;
            elm = new ExtremeLearningMachine.ELM(config);

            elm.Train();

            if (_Prov.DataType == EDataType.Predction)
            {
                
                    EPMATrain = elm.GenerateEPMAForTrain();

               
                    EMQTrain = elm.GenerateEMQForTrain();

            
                    DEVTrain = elm.GenerateDEVForTrain();

               
                    RMSETrain = elm.GenerateRMSEForTrain();
            }
            else
                SRTrain = elm.GenerateSRForTrain();
            
            elm.Validate();

            if (_Prov.DataType == EDataType.Predction)
            {
                
                EPMAVal = elm.GenerateEPMAForValidation();

                
                EMQVal = elm.GenerateEMQForValidation();

               
                DEVVal = elm.GenerateDEVForValidation();

                
                RMSEVal = elm.GenerateRMSEForValidation();
            }
            else
                SRVal = elm.GenerateSRForValidation();
            
            double fitness = 0;

            if(_Prov.DataType == EDataType.Predction)
                fitness = _EvaluateFunctionPrediction(EMQVal, DEVVal, config, particle);
            else
                fitness = _EvaluateFunctionClassification(SRVal, 1 , config, particle); // TODO: Ver se faz sentido ser variância da taxa de acerto

            particle.Fitness = fitness;
            particle.Config = config;
            particle.B = elm.GetB;
            particle.W = elm.GetW;
            particle.ELM = elm;

            if (_Prov.DataType == EDataType.Predction)
            {

                    particle.EMQTrain = EMQTrain;
                    particle.EMQValidation = EMQVal;

                    particle.DEVTrain = DEVTrain;
                    particle.DEVValidation = DEVVal;

                    particle.EPMATrain = EPMATrain;
                    particle.EPMAValidation = EPMAVal;

                    particle.RMSETrain = RMSETrain;
                    particle.RMSEValidation = RMSEVal;

            }
            else
            {
                particle.SRTrain = SRTrain;

                particle.SRValidation = SRVal;
            }
        }

        private double _EvaluateFunctionClassification(double SR, double VAR, ELMConfiguration config, HSParticle particle)
        {
            double fitness = 0;

            switch (_EvaluationFunctionType)
            {
                case EEvaluationFunctionType.Weight:
                    double EMQWeight = 5;
                    double InputWeight = 2;
                    double HiddenNodesWeight = 3;

                    if (SR != 0)
                    {
                        fitness = (EMQWeight * SR) /
                                 (                                  
                                   (InputWeight * (_MaxInputNodes / config.Prov.DataSet[0].Input.Length))
                                   +
                                   (HiddenNodesWeight * (_MaxHiddenNodes / config.HidenNodesNumber))
                                 );
                    }
                    break;

                case EEvaluationFunctionType.PSE:
                    fitness = SR + (2 * VAR * particle.GetFlagCountFromSubListValues((int)_MaxInputNodes) / _Prov.ValidationSetLines);
                    break;
            }

            return fitness;
        }
        private double _EvaluateFunctionPrediction(double EMQ, double VAR, ELMConfiguration config, HSParticle particle)
        {           
            double fitness = 0;

            switch (_EvaluationFunctionType)
            {
                case EEvaluationFunctionType.Weight:
                    //double InputWeight = 10;
                    //double HiddenNodesWeight = 0.5;

                    double emq = EMQ;
                    double input = (double)config.Prov.DataSet[0].Input.Length / (double)_MaxInputNodes;
                    double hidden =  (double)config.HidenNodesNumber / (double)_MaxHiddenNodes;


                     fitness = emq;
                     fitness += _DoInputSearch ? input + hidden : 0;

                     //fitness = 1 /
                     //         (
                     //           (EMQWeight * EMQ)
                     //           +
                     //           (InputWeight * (_MaxInputNodes / config.Prov.DataSet[0].Input.Length))
                     //           +
                     //           (HiddenNodesWeight * (_MaxHiddenNodes / config.HidenNodesNumber))
                     //         );
                    break;

                case EEvaluationFunctionType.PSE:
                    fitness = EMQ + (2 * VAR * particle.GetFlagCountFromSubListValues((int)_MaxInputNodes) / _Prov.ValidationSetLines) + (config.HidenNodesNumber / _MaxHiddenNodes);
                    break;
            }
           
            return fitness;
        }

        private ELMConfiguration _GetELMConfigurationFromHSParticle(HSParticle particle)
        {
            int inputNodesLengh = particle.GetFlagCountFromSubListValues((int)_MaxInputNodes);
            Data[] dataSet = new Data[_Prov.DataSetLines];
            int index = 0;

            for (int i = 0; i < _Prov.DataSetLines; i++)
            {
                index = 0;
                dataSet[i] = new Data(new double[inputNodesLengh], new double[_Prov.OutputsN]);
                dataSet[i].Output = _Prov.DataSet[i].Output;
                    
                for (int k = 0; k < _MaxInputNodes; k++)
                {
                    if(particle.Values[k] == 1)
                    {
                        dataSet[i].Input[index] = _Prov.DataSet[i].Input[k];
                        index++;
                    }
                }
            }

            int hiddenNodes = particle.GetHiddenNodes((int)_MaxInputNodes, _MaxHiddenNodes);

            DataProvider prov = new DataProvider(dataSet, _Prov.DataType);
            prov.SplitData();
            prov.MaxClassificationClass = _Prov.MaxClassificationClass;

            return new ELMConfiguration(prov , hiddenNodes, _Seed, _ActivationFunctionType, _Prov.DataType); 
        }
    }
    #region Auxiliar Classes
    public enum EEvaluationFunctionType
    {
        Weight = 1,

        PSE = 2,
    }
    #endregion
}
